--[[--------------------------------------------------------------------
	GridCodeState.lua
----------------------------------------------------------------------]]

local GRIDCODE, GridCode = ...
local L = GridCode.L
local GridCodeRoster = GridCode:GetModule("GridCodeRoster")

local GridCodeState = GridCode:NewModule("GridCodeState")

GridCodeState.modulePrototype = {
	core = GridCodeState,
	Debug = GridCode.Debug,
}

function GridCodeState.modulePrototype:OnInitialize()
	if not self.db then
		self.db = GridCode.db:RegisterNamespace(self.moduleName, { profile = self.defaultDB or { } })
	end

	self.debugFrame = GridCodeState.debugFrame
	self.debugging = self.db.profile.debug

	if type(self.PostInitialize) == "function" then
		self:PostInitialize()
	end
end

function GridCodeState.modulePrototype:OnEnable()
	for state, module in GridCodeState:RegisteredStateIterator() do
		if module == self.moduleName then
			if self.OnStateEnable then
				self:OnStateEnable(state)
			end
		end
	end

	if type(self.PostEnable) == "function" then
		self:PostEnable()
	end
end

function GridCodeState.modulePrototype:OnDisable()
	for state, module in GridCodeState:RegisteredStateIterator() do
		if module == self.moduleName then
			if self.OnStateDisable then
				self:OnStateDisable(state)
			end
		end
	end

	if type(self.PostDisable) == "function" then
		self:PostDisable()
	end
end

function GridCodeState.modulePrototype:Reset()
	self.debugging = self.db.profile.debug
	self:Debug("Reset")

	if type(self.PostReset) == "function" then
		self:PostReset()
	end
end

function GridCodeState.modulePrototype:InitializeSettings()
	GridCodeState:Debug("InitializeSettings", self.moduleName)
	if self.extraSettings then
		for name, option in pairs(self.extraSettings) do
			self.settings.args[name] = option
		end
	end
end

function GridCodeState.modulePrototype:RegisterState(state, desc, settings, inMainMenu, order)
	GridCodeState:RegisterState(state, desc, self.moduleName or true)

	if inMainMenu then
		GridCodeState:Debug("GridCodeState:RegisterState() : inMainMenu = true ", self.moduleName)
	else
		if not self.settings then
			self:InitializeSettings()
		end
		GridCodeState.settings.args[self.moduleName] = self.settings
	end
end

function GridCodeState.modulePrototype:UnregisterState(state)
	GridCodeState:UnregisterState(state, (self.moduleName or true))
end

function GridCodeState.modulePrototype:SendStateGained(...)
	return GridCodeState:SendStateGained(...)
end

function GridCodeState.modulePrototype:SendStateLost(...)
	return GridCodeState:SendStateLost(...)
end

function GridCodeState.modulePrototype:SendStateLostAllUnits(...)
	return GridCodeState:SendStateLostAllUnits(...)
end

GridCodeState:SetDefaultModulePrototype(GridCodeState.modulePrototype)
GridCodeState:SetDefaultModuleLibraries("AceEvent-3.0")

function GridCode:NewStateModule(name, ...)
	return GridCodeState:NewModule(name, ...)
end

------------------------------------------------------------------------

GridCodeState.defaultDB = {
	debug = false,
}

------------------------------------------------------------------------

GridCodeState.settings = {
}

------------------------------------------------------------------------

function GridCodeState:OnInitialize()
	self.super.OnInitialize(self)

	self.registry = {}
	self.registryDescriptions = {}
	self.cache = {}
end

function GridCodeState:OnEnable()
	self.super.OnEnable(self)

	self:RegisterMessage("GridCode_UnitLeft", "RemoveFromCache")
end

function GridCodeState:Reset()
	self.super.Reset(self)

	GridCodeState:SendMessage("GridCode_ColorsChanged")
end

function GridCodeState:OnModuleCreated(module)
	module.super = self.modulePrototype
end

------------------------------------------------------------------------

function GridCodeState:RegisterState(state, description, moduleName)
	if not self.registry[state] then
		self:Debug("Registered", state, "("..description..")", "for", moduleName)
		self.registry[state] = (moduleName or true)
		self.registryDescriptions[state] = description
		self:SendMessage("GridCode_StateRegistered", state, description, moduleName)
	else
		-- error if state is already registered?
		self:Debug("RegisterState:", state, "is already registered.")
	end
end

function GridCodeState:UnregisterState(state, moduleName)
	local name

	if self:IsStateRegistered(state) then
		self:Debug("Unregistered", state, "for", moduleName)
		-- need to remove from cache
		for guid in pairs(self.cache) do
			self:SendStateLost(guid, state)
		end

		-- now we can remove from registry
		self.registry[state] = nil
		self.registryDescriptions[state] = nil
		self:SendMessage("GridCode_StateUnregistered", state)
	end
end

function GridCodeState:IsStateRegistered(state)
	return (self.registry and
		self.registry[state] and
		true)
end

function GridCodeState:RegisteredStateIterator()
	local state
	local gsreg = self.registry
	local gsregdescr = self.registryDescriptions
	return function()
		state = next(gsreg, state)
		return state, gsreg[state], gsregdescr[state]
	end
end

------------------------------------------------------------------------

function GridCodeState:SendStateGained(guid, state, priority, range, color, text, value, maxValue, texture, start, duration, stack, texCoords)
	self:Debug("GridCodeState", "SendStateGained", guid, state, text, value, maxValue)
	if not guid then return end

	local cache = self.cache
	local cached

	if color and not type(color) == "table" then
		self:Debug("color is not a table for", state)
	end

	if range and type(range) ~= "number" then
		self:Debug("range is not a number for", state)
	end

	if type(texture) == "string" and type(texCoords) ~= "table" then
		self:Debug("texCoords is not a table for", state)
		texCoords = nil
	end

	if text == nil then
		text = ""
	end

	-- create cache for unit if needed
	if not cache[guid] then
		cache[guid] = {}
	end

	if not cache[guid][state] then
		cache[guid][state] = {}
	end

	cached = cache[guid][state]

	-- if no changes were made, return rather than triggering an event
	if cached
		and cached.priority == priority
		and cached.range == range
		and cached.color == color
		and cached.text == text
		and cached.value == value
		and cached.maxValue == maxValue
		and cached.texture == texture
		and cached.start == start
		and cached.duration == duration
		and cached.stack == stack
		and cached.texCoords == texCoords
	then
		return
	end

	-- update cache
	cached.priority = priority
	cached.range = range
	cached.color = color
	cached.text = text
	cached.value = value
	cached.maxValue = maxValue
	cached.texture = texture
	cached.start = start
	cached.duration = duration
	cached.stack = stack
	cached.texCoords = texCoords

	self:SendMessage("GridCode_StateGained", guid, state, priority, range, color, text, value, maxValue, texture, start, duration, stack, texCoords)
end

function GridCodeState:SendStateLost(guid, state)
	if not guid then return end

	-- if state isn't cached, don't send state lost event
	if (not self.cache[guid]) or (not self.cache[guid][state]) then
		return
	end

	self.cache[guid][state] = nil

	self:SendMessage("GridCode_StateLost", guid, state)
end

function GridCodeState:SendStateLostAllUnits(state)
	for guid in pairs(self.cache) do
		self:SendStateLost(guid, state)
	end
end

function GridCodeState:RemoveFromCache(event, guid)
	self.cache[guid] = nil
end

function GridCodeState:GetCachedState(guid, state)
	local cache = self.cache
	return (cache[guid] and cache[guid][state])
end

function GridCodeState:CachedStateIterator(state)
	local cache = self.cache
	local guid

	if state then
		-- iterator for a specific state
		return function()
			guid = next(cache, guid)

			-- we reached the end early?
			if guid == nil then
				return nil
			end

			while cache[guid][state] == nil do
				guid = next(cache, guid)

				if guid == nil then
					return nil
				end
			end

			return guid, state, cache[guid][state]
		end
	else
		-- iterator for all units, all statees
		return function()
			state = next(cache[guid], state)

			-- find the next unit with a state
			while not state do
				guid = next(cache, guid)

				if guid then
					state = next(cache[guid], state)
				else
					return nil
				end
			end

			return guid, state, cache[guid][state]
		end
	end
end
